﻿using System.Collections;
using System.Collections.Generic;
using System.Windows.Controls;
using System.Windows.Markup;

namespace System.Windows.Printing.Reporting
{
    
    public class Report : FrameworkElement
    {
        public DataTemplate PageHeaderTemplate
        {
            get { return (DataTemplate)GetValue(PageHeaderTemplateProperty); }
            set { SetValue(PageHeaderTemplateProperty, value); }
        }

        public static readonly DependencyProperty PageHeaderTemplateProperty =
            DependencyProperty.Register("PageHeaderTemplate", typeof(DataTemplate), typeof(Report), new PropertyMetadata(null));


        public DataTemplate PageFooterTemplate
        {
            get { return (DataTemplate)GetValue(PageFooterTemplateProperty); }
            set { SetValue(PageFooterTemplateProperty, value); }
        }

        public static readonly DependencyProperty PageFooterTemplateProperty =
            DependencyProperty.Register("PageFooterTemplate", typeof(DataTemplate), typeof(Report), new PropertyMetadata(null));


        public DataTemplate ItemTemplate
        {
            get { return (DataTemplate)GetValue(ItemTemplateProperty); }
            set { SetValue(ItemTemplateProperty, value); }
        }

        public static readonly DependencyProperty ItemTemplateProperty =
            DependencyProperty.Register("ItemTemplate", typeof(DataTemplate), typeof(Report), new PropertyMetadata(null));




        public DataTemplate ReportFooterTemplate
        {
            get { return (DataTemplate)GetValue(ReportFooterTemplateProperty); }
            set { SetValue(ReportFooterTemplateProperty, value); }
        }

        public static readonly DependencyProperty ReportFooterTemplateProperty =
            DependencyProperty.Register("ReportFooterTemplate", typeof(DataTemplate), typeof(Report), new PropertyMetadata(null));



        public IEnumerable ItemsSource
        {
            get { return (IEnumerable)GetValue(ItemsSourceProperty); }
            set { SetValue(ItemsSourceProperty, value); }
        }

        public static readonly DependencyProperty ItemsSourceProperty =
            DependencyProperty.Register("ItemsSource", typeof(IEnumerable), typeof(Report), new PropertyMetadata(null));




        //public ItemCollection ItemsOnCurrentPage
        //{
        //    get { return (ItemCollection)GetValue(ItemsOnCurrentPageProperty); }
        //    private set { SetValue(ItemsOnCurrentPageProperty, value); }
        //}

        //public static readonly DependencyProperty ItemsOnCurrentPageProperty =
        //    DependencyProperty.Register("ItemsOnCurrentPage", typeof(ItemCollection), typeof(Report), new PropertyMetadata(null));


        public string Title
        {
            get { return (string)GetValue(TitleProperty); }
            set { SetValue(TitleProperty, value); }
        }

        public static readonly DependencyProperty TitleProperty =
            DependencyProperty.Register("Title", typeof(string), typeof(Report), new PropertyMetadata("Report"));




        public int CurrentPageNumber
        {
            get { return (int)GetValue(CurrentPageNumberProperty); }
            private set { SetValue(CurrentPageNumberProperty, value); }
        }

        public static readonly DependencyProperty CurrentPageNumberProperty =
            DependencyProperty.Register("CurrentPageNumber", typeof(int), typeof(Report), new PropertyMetadata(0));



        public int TotalPageCount
        {
            get { return (int)GetValue(TotalPageCountProperty); }
            private set { SetValue(TotalPageCountProperty, value); }
        }

        public static readonly DependencyProperty TotalPageCountProperty =
            DependencyProperty.Register("TotalPageCount", typeof(int), typeof(Report), new PropertyMetadata(0));



        private PrintDocument _printDocument = new PrintDocument();
        public PrintDocument PrintDocument
        {
            get { return _printDocument; }
        }


        public void Print()
        {
            CurrentPageNumber = 0;

            int pageIndex = 0;

            _printDocument.PrintPage += (s, e) =>
            {
                if (pageIndex == 0)
                {
                    BuildReport(e.PrintableArea);
                    TotalPageCount = _pageTrees.Count;
                    CurrentPageNumber = 0;
                }

                if (_pageTrees.Count > 0)
                {
                    CurrentPageNumber++;
                    e.PageVisual = _pageTrees[pageIndex];
                }

                e.HasMorePages = pageIndex < _pageTrees.Count - 1;

                pageIndex++;
            };


            _printDocument.BeginPrint += (s, e) =>
            {
                OnBeginPrint(EventArgs.Empty);
            };

            _printDocument.EndPrint += (s, e) =>
            {
                OnEndPrint(EventArgs.Empty);
            };

            _printDocument.Print(Title);
        }


        public event EventHandler EndPrint;
        protected void OnEndPrint(EventArgs args)
        {
            if (EndPrint != null)
                EndPrint(this, args);
        }

        public event EventHandler BeginPrint;
        protected void OnBeginPrint(EventArgs args)
        {
            if (BeginPrint != null)
                BeginPrint(this, args);
        }

        public event EventHandler EndBuildReport;
        protected void OnEndBuildReport(EventArgs args)
        {
            if (EndBuildReport != null)
                EndBuildReport(this, args);
        }

        public event EventHandler BeginBuildReport;
        protected void OnBeginBuildReport(EventArgs args)
        {
            if (BeginBuildReport != null)
                BeginBuildReport(this, args);
        }

        public event EventHandler EndBuildReportItem;
        protected void OnEndBuildReportItem(EventArgs args)
        {
            if (EndBuildReportItem != null)
                EndBuildReportItem(this, args);
        }

        public event EventHandler<PrintingEventArgs> BeginBuildReportItem;
        protected void OnBeginBuildReportItem(PrintingEventArgs args)
        {
            if (BeginBuildReportItem != null)
                BeginBuildReportItem(this, args);
        }

        public event EventHandler<PrintingEventArgs> BeginBuildReportFooter;
        protected void OnBeginBuildReportFooter(PrintingEventArgs args)
        {
            if (BeginBuildReportFooter != null)
                BeginBuildReportFooter(this, args);
        }



        private List<UIElement> _pageTrees = new List<UIElement>();

        // Pre-calculates pages. This allows for total page count
        // as well as better handling of page breaks, and eventual
        // inclusion of groups and whatnot. If this takes too long, though
        // it'll exceed the timeout and make the printing fail in a 
        // sandboxed silverlight application. Alternative would be to make
        // printing a two-step operation: First, build report, Second, user 
        // clicks button to do the actual printing. That approach 
        // introduces issues with data changing in-between the steps.
        private void BuildReport(Size printableArea)
        {
            _pageTrees.Clear();
            CurrentPageNumber = 0;

            OnBeginBuildReport(EventArgs.Empty);

            if (ItemsSource == null)
                return;

            IEnumerable reportItems = ItemsSource;
            IEnumerator reportItemsEnumerator = reportItems.GetEnumerator();

            reportItemsEnumerator.Reset();

            StackPanel itemsPanel = null;
            Grid pagePanel = null;
            Size itemsPanelMaxSize = new Size();

            // create first page
            pagePanel = GetNewPage(printableArea, out itemsPanel, out itemsPanelMaxSize);

            // add the items
            while (reportItemsEnumerator.MoveNext())
            {
                PrintingEventArgs args = new PrintingEventArgs();
                args.DataContext = reportItemsEnumerator.Current;
                OnBeginBuildReportItem(args);

                // create row. Set data context.
                FrameworkElement row = ItemTemplate.LoadContent() as FrameworkElement;
                row.DataContext = args.DataContext;
                row.Measure(printableArea);

                // create a new page if we're out of room here
                if (row.DesiredSize.Height + itemsPanel.DesiredSize.Height > itemsPanelMaxSize.Height)
                {
                    pagePanel = GetNewPage(printableArea, out itemsPanel, out itemsPanelMaxSize);
                }

                itemsPanel.Children.Add(row);
                itemsPanel.Measure(printableArea);
            }

            // create report footer
            PrintingEventArgs reportFooterEventArgs = new PrintingEventArgs();
            reportFooterEventArgs.DataContext = this;
            OnBeginBuildReportFooter(reportFooterEventArgs);

            FrameworkElement reportFooter = ReportFooterTemplate.LoadContent() as FrameworkElement;
            if (reportFooter != null)
            {
                reportFooter.DataContext = reportFooterEventArgs.DataContext;
                reportFooter.Measure(printableArea);

                // fit the footer into the report
                if (reportFooter.DesiredSize.Height + itemsPanel.DesiredSize.Height > itemsPanelMaxSize.Height)
                {
                    pagePanel = GetNewPage(printableArea, out itemsPanel, out itemsPanelMaxSize);
                }

                itemsPanel.Children.Add(reportFooter);
            }


            OnEndBuildReport(EventArgs.Empty);

        }



        private Grid GetNewPage(Size printableArea, out StackPanel itemsPanel, out Size itemsPanelMaxSize)
        {
            CurrentPageNumber++;


            Grid pagePanel = new Grid();
            RowDefinition headerRow = new RowDefinition();
            headerRow.Height = GridLength.Auto;

            RowDefinition itemsRow = new RowDefinition();
            itemsRow.Height = new GridLength(1, GridUnitType.Star);

            RowDefinition footerRow = new RowDefinition();
            footerRow.Height = GridLength.Auto;

            pagePanel.RowDefinitions.Add(headerRow);
            pagePanel.RowDefinitions.Add(itemsRow);
            pagePanel.RowDefinitions.Add(footerRow);

            // header
            FrameworkElement header = PageHeaderTemplate.LoadContent() as FrameworkElement;
            header.DataContext = this;
            Grid.SetRow(header, 0);
            pagePanel.Children.Add(header);
            header.Measure(new Size(printableArea.Width, printableArea.Height));


            // create body to fit in between
            itemsPanel = new StackPanel();
            itemsPanel.Orientation = Orientation.Vertical;
            itemsPanel.HorizontalAlignment = HorizontalAlignment.Stretch;
            itemsPanel.VerticalAlignment = VerticalAlignment.Top;
            Grid.SetRow(itemsPanel, 1);
            pagePanel.Children.Add(itemsPanel);


            // create footer
            FrameworkElement footer = PageFooterTemplate.LoadContent() as FrameworkElement;
            footer.DataContext = this;
            Grid.SetRow(footer, 2);
            pagePanel.Children.Add(footer);
            footer.Measure(new Size(printableArea.Width, printableArea.Height));


            itemsPanelMaxSize = new Size(printableArea.Width, printableArea.Height - footer.DesiredSize.Height - header.DesiredSize.Height);

            _pageTrees.Add(pagePanel);

            return pagePanel;
        }




    }
}
